home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue24 / system / Zip.pas next >
Encoding:
Pascal/Delphi Source File  |  1997-07-02  |  18.4 KB  |  571 lines

  1. unit Zip;
  2.  
  3. interface
  4.  
  5. {$A-}
  6.  
  7. uses WinTypes, WinProcs, SysUtils, Classes, Match;
  8.  
  9. type
  10.     EZipErr = class (Exception);
  11.  
  12.     SortType = (sRaw, sFullName, sFileName, sPathName, sCompressedSize,
  13.                 sOriginalSize, sCompressRatio, sDate);
  14.  
  15.     CompressType = (Stored, Shrunk, Reduce1, Reduce2, Reduce3, Reduce4,
  16.                     Imploded, ResTokenised, Deflated, ResEnhancedDeflate,
  17.                     ResPKLibrary);
  18.  
  19.     TZipFile = class (TObject)
  20.     private
  21.         Dir: TList;
  22.         SortMap: TList;
  23.         fd: Integer;
  24.         fSort: SortType;
  25.         pTail: Pointer;
  26.         SelFiles: Integer;
  27.         fName: String;
  28.         fExtractDir: String;
  29.         fPassword: String;
  30.         fLowerCaseNames: Boolean;
  31.         fReverseSort: Boolean;
  32.         procedure LoadDirectory;
  33.         procedure UnloadDirectory;
  34.         function GetSigOffset (Signature: LongInt): LongInt;
  35.         function GetDirectoryEntry (Idx: Integer): Pointer;
  36.         function GetFilesCount: Integer;
  37.         procedure SortFiles;
  38.         procedure DoSort (L, R: Integer);
  39.         function GetFullName (Index: Integer): String;
  40.         function GetFileName (Index: Integer): String;
  41.         function GetPathName (Index: Integer): String;
  42.         function GetEncrypted (Index: Integer): Boolean;
  43.         function GetCompressMethod (Index: Integer): CompressType;
  44.         function GetCompressMethodName (Index: Integer): String;
  45.         function GetCompressionRatio (Index: Integer): Integer;
  46.         function GetDiskNumber (Index: Integer): Integer;
  47.         function GetCrc32 (Index: Integer): LongInt;
  48.         function GetCompressedSize (Index: Integer): LongInt;
  49.         function GetOriginalSize (Index: Integer): LongInt;
  50.         function GetDateTime (Index: Integer): TDateTime;
  51.         function GetCommentLength (Index: Integer): Word;
  52.         procedure SetZipName (const FileName: String);
  53.         procedure SetSortType (Val: SortType);
  54.         procedure SetReverseSort (Val: Boolean);
  55.     public
  56.         constructor Create (const FileName: String);
  57.         destructor Destroy; override;
  58.         procedure Reset;
  59.         property FullName [Index: Integer]: String read GetFullName; default;
  60.         property FileName [Index: Integer]: String read GetFileName;
  61.         property PathName [Index: Integer]: String read GetPathName;
  62.         property Encrypted [Index: Integer]: Boolean read GetEncrypted;
  63.         property DiskNumber [Index: Integer]: Integer read GetDiskNumber;
  64.         property Crc32 [Index: Integer]: LongInt read GetCrc32;
  65.         property CompressMethod [Index: Integer]: CompressType read GetCompressMethod;
  66.         property DateTime [Index: Integer]: TDateTime read GetDateTime;
  67.         property CompressedSize [Index: Integer]: LongInt read GetCompressedSize;
  68.         property OriginalSize [Index: Integer]: LongInt read GetOriginalSize;
  69.         property CompressMethodName [Index: Integer]: String read GetCompressMethodName;
  70.         property CommentLength [Index: Integer]: Word read GetCommentLength;
  71.         property CompressionRatio [Index: Integer]: Integer read GetCompressionRatio;
  72.     published
  73.         property ZipName: String read fName write SetZipName;
  74.         property SortStyle: SortType read fSort write SetSortType;
  75.         property ExtractDir: String read fExtractDir write fExtractDir;
  76.         property Password: String read fPassword write fPassword;
  77.         property ReverseSort: Boolean read fReverseSort write SetReverseSort default False;
  78.         property LowerCaseNames: Boolean read fLowerCaseNames write fLowerCaseNames default True;
  79.         property FilesCount: Integer read GetFilesCount;
  80.     end;
  81.  
  82. implementation
  83.  
  84. type
  85.     PTailRec = ^TailRec;
  86.     TailRec = record              { End of central dir - 'tail'        }
  87.     Signature: LongInt;           { should be $06054b50                }
  88.     ThisDisk: Word;               { # of this disk                     }
  89.     DirDisk: Word;                { # of disk with central dir start   }
  90.     NumEntries: Word;             { # of central dir entries this disk }
  91.     TotEntries: Word;             { # of central dir entries total     }
  92.     DirSize: LongInt;             { size of the central directory      }
  93.     DirOffset: LongInt;           { offset of c-dir wrt starting disk  }
  94.     BannerLength: Word;           { size of following comment (if any) }
  95. end;
  96.  
  97. type
  98.     PDirEntry = ^DirEntry;
  99.     DirEntry = record             { Central Directory entry            }
  100.     Signature: LongInt;           { should be $02014b50                }
  101.     CreatorVersion: Word;         { version of ZIP that created it     }
  102.     ExtractorVersion: Word;       { version of ZIP needed for extract  }
  103.     GenBits: Word;                { general purpose bit flags          }
  104.     CompressMethod: Word;         { compression method for this file   }
  105.     DateTime: LongInt;            { file modification date/time        }
  106.     crc32: LongInt;               { 32-bit file CRC                    }
  107.     CompressedSize: LongInt;      { compressed size of file            }
  108.     OriginalSize: LongInt;        { uncompressed size of file          }
  109.     FileNameLen: Word;            { length of filename                 }
  110.     ExtraLen: Word;               { length of extra info               }
  111.     CommentLen: Word;             { length of comment stuff            }
  112.     DiskNumStart: Word;           { starting disk number               }
  113.     IFileAttribs: Word;           { File attributes                    }
  114.     XFileAttribs: LongInt;        { External file attributes           }
  115.     HeaderPos: LongInt;           { offset of local header             }
  116. end;
  117.  
  118. function GetDirEntrySize (const Entry: DirEntry): Integer;
  119. begin
  120.     with Entry do Result := sizeof (DirEntry) + FileNameLen + ExtraLen + CommentLen;
  121. end;
  122.  
  123. function IsValidTailPos (fd: Integer; tailPos: LongInt): Bool;
  124. var
  125.     tail: TailRec;
  126. begin
  127.     { This function is needed to cope with nested ZIP files  }
  128.     { Without it, we might accidentally accept a tail marker }
  129.     { inside a nested ZIP rather than the ZIP's own marker ! }
  130.  
  131.     Result := False;
  132.     _llseek (fd, tailPos, 0);
  133.     _lread (fd, @tail, sizeof (TailRec));
  134.     if tail.Signature = $06054b50 then
  135.     begin
  136.         _llseek (fd, tail.DirOffset, 0);
  137.         _lread (fd, @tail, sizeof (LongInt));
  138.         Result := tail.Signature = $02014b50;
  139.     end;
  140. end;
  141.  
  142. function FindSig (fd: Integer; buff: PChar; len: Integer; fPos, Signature: LongInt): integer;
  143. var
  144.     p: PChar;
  145.     pp: ^LongInt absolute p;
  146. begin
  147.     Result := -1;
  148.     if len <> 0 then begin
  149.         p := buff;
  150.         while len <> 0 do begin
  151.             if (pp^ = Signature) and IsValidTailPos (fd, fpos + p - buff) then begin
  152.                 Result := p - buff;
  153.                 Exit;
  154.             end;
  155.  
  156.             Inc (p);
  157.             Dec (len);
  158.         end;
  159.     end;
  160. end;
  161.  
  162. { These utility routines extract various fields from a DirEntry }
  163.  
  164. function DirGetFullName (pde: PDirEntry): String;
  165. var
  166.     Idx: Integer;
  167. begin
  168.     Result := '';
  169.     if pde <> Nil then with pde^ do begin
  170.         {$IFDEF WIN32} SetLength (Result, FileNameLen); {$ELSE} Result[0] := Chr(FileNameLen); {$ENDIF}
  171.         Move ((PChar (pde) + sizeof (DirEntry))^, Result [1], FileNameLen);
  172.         { Massage UNIX forward slashes to Wintel backslashes }
  173.         for Idx := 1 to Length (Result) do
  174.             if Result [Idx] = '/' then Result [Idx] := '\';
  175.     end;
  176. end;
  177.  
  178. function DirGetCompRatio (pde: PDirEntry): Double;
  179. begin
  180.     Result := 0;
  181.     if pde <> Nil then with pde^ do
  182.         if OriginalSize <> 0 then
  183.             Result := ((OriginalSize - CompressedSize) * 100) / OriginalSize;
  184. end;
  185.  
  186. { TZipFile }
  187.  
  188. constructor TZipFile.Create (const FileName: String);
  189. begin
  190.     fd := -1;
  191.     SortMap := TList.Create;
  192.     fLowerCaseNames := True;
  193.     fReverseSort := False;
  194.     SetZipName (FileName);
  195. end;
  196.  
  197. procedure TZipFile.SetZipName (const FileName: String);
  198. var
  199.     tail: TailRec;
  200.     tailPos: LongInt;
  201.     szName: array [0..255] of Char;
  202. begin
  203.     UnloadDirectory;
  204.     fName := '';  fPassword := '';
  205.     { If filename is empty, just exit }
  206.     if FileName = '' then Exit;
  207.  
  208.     { Get the filename and make sure it has a proper extension }
  209.     StrPCopy (szName, FileName);
  210.     if StrPos (szName, '.') = Nil then lstrcat (szName, '.zip');
  211.  
  212.     { Now try to open the file }
  213.     fd := _lopen (szName, of_Read or of_Share_Deny_Write);
  214.     if fd = -1 then raise EZipErr.Create ('Cannot open specified file');
  215.     fName := StrPas (szName);
  216.  
  217.     { OK - it's there, but is it a valid ZIP file ? }
  218.     tailPos := GetSigOffset ($06054b50);
  219.     if tailPos < 0 then raise EZipErr.Create ('Not a valid ZIP file');
  220.  
  221.     { Found the directory tail - ensure no disk spanning }
  222.     _llseek (fd, tailPos, 0);
  223.     _lread (fd, @tail, sizeof (TailRec));
  224.     if (tail.ThisDisk <> 0) or (tail.DirDisk <> 0) then raise EZipErr.Create ('Disk spanning not yet implemented');
  225.  
  226.     { Read directory tail and banner into our data structure }
  227.     GetMem (pTail, sizeof (TailRec) + tail.BannerLength);
  228.     _llseek (fd, tailPos, 0);
  229.     _lread (fd, PChar (pTail), sizeof (TailRec) + tail.BannerLength);
  230.  
  231.     { Now get the central directory & ensure all files selected }
  232.     LoadDirectory;
  233. end;
  234.  
  235. destructor TZipFile.Destroy;
  236. begin
  237.     UnloadDirectory;
  238.     SortMap.Free;
  239.     Inherited Destroy;
  240. end;
  241.  
  242. procedure TZipFile.LoadDirectory;
  243. var
  244.     p: PChar;
  245.     de: DirEntry;
  246.     sz, Idx: Integer;
  247.  
  248.     function NonBlankEntry: Boolean;
  249.     begin
  250.         Result := (de.CompressedSize <> 0) or
  251.                   (de.OriginalSize <> 0) or
  252.                   (de.CompressMethod <> 0);
  253.     end;
  254.  
  255. begin
  256.     { Initialize directory TList }
  257.     Dir := TList.Create;
  258.     Dir.Capacity := PTailRec(pTail)^.NumEntries;
  259.     { Seek to start of file }
  260.     _llseek (fd, PTailRec(pTail)^.DirOffset, 0);
  261.     { Read each entry in consecutively }
  262.     for Idx := 0 to PTailRec(pTail)^.NumEntries - 1 do begin
  263.         _lread (fd, @de, sizeof (de));
  264.         sz := GetDirEntrySize (de);
  265.         GetMem (p, sz);
  266.         Move (de, p^, sizeof (de));
  267.         _lread (fd, p + sizeof (de), sz - sizeof (de));
  268.  
  269.         { If this is a blank 'directory-marker' record, then skip it }
  270.         if NonBlankEntry then Dir.Add (p) else FreeMem (p, sz);
  271.     end;
  272.  
  273.     Reset;
  274. end;
  275.  
  276. function TZipFile.GetDirectoryEntry (Idx: Integer): Pointer;
  277. begin
  278.     if Dir = Nil then raise EZipErr.Create ('No ZIP file specified');
  279.     if fReverseSort then Idx := SortMap.Count - 1 - Idx;
  280.     Result := SortMap [Idx];
  281. end;
  282.  
  283. procedure TZipFile.DoSort (L, R: Integer);
  284. var
  285.     P: Pointer;
  286.     I, J: Integer;
  287.  
  288.     function SortCompare (Key1, Key2: PDirEntry): Integer;
  289.     var
  290.         D1, D2: Double;
  291.         S1, S2: String;
  292.     begin
  293.         D1 := 0; D2 := 0; Result := 0; { Just to shut the compiler up }
  294.         case fSort of
  295.             sFullName, sFileName, sPathName:
  296.             begin
  297.                 S1 := DirGetFullName (Key1);
  298.                 S2 := DirGetFullName (Key2);
  299.                 if fSort = sFileName then begin
  300.                     S1 := ExtractFileName (S1);
  301.                     S2 := ExtractFileName (S2);
  302.                 end;
  303.                 if fSort = sPathName then begin
  304.                     S1 := ExtractFilePath (S1);
  305.                     S2 := ExtractFilePath (S2);
  306.                 end;
  307.                 Result := CompareText (S1, S2);
  308.             end;
  309.  
  310.             sDate, sCompressedSize, sOriginalSize, sCompressRatio:
  311.             begin
  312.                 if fSort = sDate then begin
  313.                     D1 := FileDateToDateTime (Key1^.DateTime);
  314.                     D2 := FileDateToDateTime (Key2^.DateTime);
  315.                 end;
  316.                 if fSort = sCompressedSize then begin
  317.                     D1 := Key1^.CompressedSize;
  318.                     D2 := Key2^.CompressedSize;
  319.                 end;
  320.                 if fSort = sOriginalSize then begin
  321.                     D1 := Key1^.OriginalSize;
  322.                     D2 := Key2^.OriginalSize;
  323.                 end;
  324.                 if fSort = sCompressRatio then begin
  325.                     D1 := DirGetCompRatio (Key1);
  326.                     D2 := DirGetCompRatio (Key2);
  327.                 end;
  328.  
  329.                 if D1 = D2 then Result := 0 else if D1 > D2 then Result := 1 else Result := -1;
  330.             end;
  331.         end;
  332.     end;
  333.  
  334. begin
  335.     repeat
  336.         I := L; J := R; P := SortMap [(L + R) shr 1];
  337.         repeat
  338.             while SortCompare (SortMap [I], P) < 0 do Inc(I);
  339.             while SortCompare (SortMap [J], P) > 0 do Dec(J);
  340.             if I <= J then begin SortMap.Exchange (I, J); Inc(I); Dec(J); end;
  341.         until I > J;
  342.         if L < J then DoSort (L, J);
  343.         L := I;
  344.     until I >= R;
  345. end;
  346.  
  347. procedure TZipFile.SortFiles;
  348. var
  349.     Idx: Integer;
  350. begin
  351.     { First, clear the sort map }
  352.     SortMap.Clear;
  353.     SortMap.Capacity := FilesCount;
  354.     { Initialise the sort map for 'sRaw' mode }
  355.     for Idx := 0 to FilesCount - 1 do SortMap.Add (Dir [Idx]);
  356.     { Now do the actual sort }
  357.      if fSort <> sRaw then DoSort (0, SortMap.Count - 1);
  358. end;
  359.  
  360. procedure TZipFile.SetSortType (Val: SortType);
  361. begin
  362.     fSort := Val;
  363.     if Dir <> Nil then SortFiles;
  364. end;
  365.  
  366. procedure TZipFile.SetReverseSort (Val: Boolean);
  367. begin
  368.     fReverseSort := Val;
  369. end;
  370.  
  371. procedure TZipFile.Reset;
  372. var
  373.     idx: Integer;
  374. begin
  375.     SetSortType (fSort);
  376.     SelFiles := FilesCount;
  377.     for idx := 0 to SelFiles - 1 do
  378.         PDirEntry (GetDirectoryEntry (idx))^.Signature := 1;
  379. end;
  380.  
  381. procedure TZipFile.UnloadDirectory;
  382.  
  383.     procedure FreeList (var List: TList);
  384.     var
  385.         p: Pointer;
  386.         Idx: Integer;
  387.     begin
  388.         if List <> Nil then
  389.         begin
  390.             for Idx := 0 to List.Count - 1 do
  391.             begin
  392.                 p := List.Items [Idx];
  393.                 FreeMem (p, GetDirEntrySize (PDirEntry(p)^));
  394.             end;
  395.  
  396.             List.Free;
  397.             List := Nil;
  398.         end;
  399.     end;
  400.  
  401. begin
  402.     FreeList (Dir);
  403.  
  404.     if pTail <> Nil then begin
  405.         FreeMem (pTail, sizeof (TailRec) + PTailRec(pTail)^.BannerLength);
  406.         pTail := Nil;
  407.     end;
  408.  
  409.     if fd <> -1 then begin
  410.         _lclose (fd);
  411.         fd := -1;
  412.     end;
  413. end;
  414.  
  415. function TZipFile.GetSigOffset (Signature: LongInt): LongInt;
  416. const
  417.     InBufferSize = 8192;            { for sig searching }
  418. var
  419.     buff: PChar;
  420.     fs, pos: LongInt;
  421.     bp, bytesread: Integer;
  422. begin
  423.     GetMem (buff, InBufferSize);
  424.     try
  425.         fs := _llseek (fd, 0, 2);
  426.         if fs <= InBuffersize then pos := 0 else pos := fs - InBufferSize;
  427.         _llseek (fd, pos, 0);
  428.  
  429.         { Get initial buffer content }
  430.         _lread (fd, buff, InBufferSize);
  431.         bp := FindSig (fd, buff, InBufferSize, pos, Signature);
  432.  
  433.         { This is the main search loop... }
  434.         while (bp < 0) and (pos > 0) do
  435.         begin
  436.             Move (buff, buff [InBufferSize - 4], 4);
  437.             Dec (pos, InBufferSize - 4);
  438.             if pos < 0 then pos := 0;
  439.             _llseek (fd, pos, 0);
  440.             bytesRead := _lread (fd, buff, InBufferSize - 4);
  441.             if bytesRead < InBufferSize - 4 then Move (buff [InBufferSize - 4], buff [BytesRead], 4);
  442.             if bytesRead > 0 then
  443.             begin
  444.                 Inc (bytesRead, 4);
  445.                 bp := FindSig (fd, buff, bytesRead, pos, Signature);
  446.             end;
  447.         end;
  448.  
  449.         if bp < 0 then GetSigOffset := -1 else GetSigOffset := pos + bp;
  450.     finally
  451.         FreeMem (buff, InBufferSize);
  452.     end;
  453. end;
  454.  
  455. function TZipFile.GetFilesCount: Integer;
  456. begin
  457.     if Dir = Nil then raise EZipErr.Create ('No ZIP file specified');
  458.     Result := Dir.Count;
  459. end;
  460.  
  461. function TZipFile.GetFileName (Index: Integer): String;
  462. begin
  463.     Result := ExtractFileName (GetFullName (Index));
  464.     if fLowerCaseNames then Result := LowerCase (Result);
  465. end;
  466.  
  467. function TZipFile.GetPathName (Index: Integer): String;
  468. begin
  469.     Result := ExtractFilePath (GetFullName (Index));
  470. end;
  471.  
  472. function TZipFile.GetFullName (Index: Integer): String;
  473. begin
  474.     Result := DirGetFullName (GetDirectoryEntry (Index));
  475. end;
  476.  
  477. function TZipFile.GetDateTime (Index: Integer): TDateTime;
  478. var
  479.     pde: PDirEntry;
  480. begin
  481.     Result := 0;
  482.     pde := GetDirectoryEntry (Index);
  483.     if pde <> Nil then Result := FileDateToDateTime (pde^.DateTime);
  484. end;
  485.  
  486. function TZipFile.GetEncrypted (Index: Integer): Boolean;
  487. var
  488.     pde: PDirEntry;
  489. begin
  490.     Result := False;
  491.     pde := GetDirectoryEntry (Index);
  492.     if pde <> Nil then Result := (pde^.GenBits and 1) <> 0;
  493. end;
  494.  
  495. function TZipFile.GetCompressionRatio (Index: Integer): Integer;
  496. begin
  497.     Result := Round (DirGetCompRatio (GetDirectoryEntry (Index)));
  498. end;
  499.  
  500. function TZipFile.GetCompressedSize (Index: Integer): LongInt;
  501. var
  502.     pde: PDirEntry;
  503. begin
  504.     Result := 0;
  505.     pde := GetDirectoryEntry (Index);
  506.     if pde <> Nil then Result := pde^.CompressedSize;
  507. end;
  508.  
  509. function TZipFile.GetOriginalSize (Index: Integer): LongInt;
  510. var
  511.     pde: PDirEntry;
  512. begin
  513.     Result := 0;
  514.     pde := GetDirectoryEntry (Index);
  515.     if pde <> Nil then Result := pde^.OriginalSize;
  516. end;
  517.  
  518. function TZipFile.GetCompressMethod (Index: Integer): CompressType;
  519. var
  520.     pde: PDirEntry;
  521. begin
  522.     Result := Stored;
  523.     pde := GetDirectoryEntry (Index);
  524.     if pde <> Nil then Result := CompressType (pde^.CompressMethod);
  525. end;
  526.  
  527. function TZipFile.GetDiskNumber (Index: Integer): Integer;
  528. var
  529.     pde: PDirEntry;
  530. begin
  531.     Result := 1;
  532.     pde := GetDirectoryEntry (Index);
  533.     if pde <> Nil then Result := pde^.DiskNumStart;
  534. end;
  535.  
  536. function TZipFile.GetCrc32 (Index: Integer): LongInt;
  537. var
  538.     pde: PDirEntry;
  539. begin
  540.     Result := 0;
  541.     pde := GetDirectoryEntry (Index);
  542.     if pde <> Nil then Result := pde^.crc32;
  543. end;
  544.  
  545. function TZipFile.GetCommentLength (Index: Integer): Word;
  546. var
  547.     pde: PDirEntry;
  548. begin
  549.     Result := 0;
  550.     pde := GetDirectoryEntry (Index);
  551.     if pde <> Nil then Result := pde^.CommentLen;
  552. end;
  553.  
  554. function TZipFile.GetCompressMethodName (Index: Integer): String;
  555. var
  556.     typ: CompressType;
  557. begin
  558.     typ := GetCompressMethod (Index);
  559.     case typ of
  560.         Stored:             Result := 'Stored';
  561.         Shrunk:             Result := 'Shrunk';
  562.         Reduce1..Reduce4:   Result := 'Reduced';
  563.         Imploded:           Result := 'Imploded';
  564.         Deflated:           Result := 'Deflated';
  565.         else                Result := Format ('Unknown(%d)', [Ord (typ)]);
  566.     end;
  567. end;
  568.  
  569. end.
  570.  
  571.